package com.webex.ta.hydra.core;

import com.thoughtworks.xstream.annotations.XStreamAlias;
import com.thoughtworks.xstream.annotations.XStreamOmitField;

import java.io.Serializable;
import java.util.*;

/**
 * Created by Cisco WebEx.
 * User: vegaz
 * Date: 2010-8-17
 * Time: 16:44:54
 */
@XStreamAlias("Command")
public class Command implements Serializable {
    @XStreamAlias("bp")
    private boolean isBreakPoint;
    @XStreamAlias("kw")
    private String keyword;
    private Vector<String> params;
    private Command parent;
    private Vector<Command> children;
    private UUID uuid;
    private int indent;
    private boolean expanded;
    private boolean enabled;
    @XStreamOmitField
    private boolean copied = false;
    @XStreamOmitField
    private Keyword runtime;
    @XStreamOmitField
    private boolean inDebug;
    @XStreamOmitField
    private boolean valid;
    @XStreamOmitField
    private Map<Integer, String> validationInfos;
    @XStreamOmitField
    private Vector<TestResult> results;
    private String annotation;

    public Command() {
        parent = null;
        children = new Vector<Command>();
        isBreakPoint = false;
        keyword = "";
        params = new Vector<String>();
        uuid = UUID.randomUUID();
        indent = 0;
        expanded = false;
        inDebug = false;
        runtime = null;
        enabled = true;
        validationInfos = new HashMap<Integer, String>();
        results = new Vector<TestResult>();
        valid = true;
    }


    public void setKeyword(String keyword) {
        this.keyword = keyword;
    }

    public String getKeyword() {
        return keyword;
    }

    public void setAnnotation(String annotation) {
        this.annotation = annotation;
    }

    public String getAnnotation() {
        return annotation;
    }

    public boolean hasAnnotation() {
        return annotation != null && !annotation.trim().equals("");
    }


    public Vector<String> getParameters() {
        return params;
    }

    public int getParameterSize() {
        return params.size();
    }

    public void addParameter(String value) {
        params.add(value);
    }

    public void insertParameter(String value, int idx) {
        params.insertElementAt(value, idx);
    }

    public void setParameter(int idx, String value) {
        if (params.size() <= idx + 1)
            params.setSize(idx + 1);
        params.set(idx, value);
    }

    public String getParameter(int idx) {
        if (params.size() <= idx + 1)
            params.setSize(idx + 1);
        return params.get(idx);
    }

    public void setBreakPoint(boolean value) {
        isBreakPoint = value;
    }

    public boolean isBreakPoint() {
        return isBreakPoint;
    }

    public UUID getUuid() {
        return uuid;
    }

    public void setUuid(UUID uuid) {
        this.uuid = uuid;
    }

    public void setEnabled(boolean v) {
        this.enabled = v;
    }

    public boolean isEnabled() {
        return enabled;
    }

    public void setCopied(boolean v) {
        this.copied = v;
    }

    public boolean isCopied() {
        return copied || isParentCopied();
    }

    private boolean isParentCopied() {
        if (hasParent()) {
            return parent.isCopied();
        } else {
            return false;
        }
    }

    public void setInDebug(boolean v) {
        this.inDebug = v;
    }

    public boolean isInDebug() {
        return inDebug;
    }

    public void setIndent(int value) {
        indent = value;
    }

    public int getIndent() {
        return indent;
    }

    public void setRuntime(Keyword runtime) {
        this.runtime = runtime;
    }

    public Keyword getRuntime() {
        return runtime;
    }

    public void setParent(Command cmd) {
        parent = cmd;
    }

    public Command getParent() {
        return parent;
    }

    public boolean hasParent() {
        return parent != null;
    }

    public void setExpanded(boolean v) {
        this.expanded = v;
    }

    public boolean isExpanded() {
        return expanded;
    }

    public void putValidationInfo(Integer part, String vi) {
        if (validationInfos == null) {
            validationInfos = new HashMap<Integer, String>();
        }
        this.validationInfos.put(part, vi);
        recalculateValid();
    }

    public String getValidationInfo(int part) {
        if (validationInfos == null)
            return null;
        return validationInfos.get(Integer.valueOf(part));
    }

    public void removeValidationInfo(int part) {
        if (validationInfos != null)
            validationInfos.remove(Integer.valueOf(part));
        recalculateValid();
    }

    public void clearValidationInfo() {
        validationInfos.clear();
        recalculateValid();
    }

    public boolean isValid() {
        return valid;
    }

    private void recalculateValid() {
        boolean selfNewValue = validationInfos.size() <= 0;
        boolean childNewValue = true;
        if (children.size() > 0) {
            for (Command child : children) {
                if (!child.isValid()) {
                    childNewValue = false;
                    break;
                }
            }
        }
        boolean newValue = selfNewValue && childNewValue;
        if (valid != newValue) {
            valid = newValue;
            if (parent != null)
                parent.recalculateValid();
        }
    }

    public Map<Integer, String> getValidationInfos() {
        return validationInfos;
    }

    public int getChildIndexOf(Command child) {
        return children.indexOf(child);
    }

    public void addChild(Command child) {
        if (child.hasParent()) {
            child.parent.removeChild(child);
        }
        child.setParent(this);
        child.setIndent(this.getIndent() + 1);
        children.add(child);
    }

    public void removeChild(Command child) {
        if (children.contains(child)) {
            children.remove(child);
            child.setParent(null);
        }
    }

    public void removeChildAt(int index) {
        Command removed = children.remove(index);
        removed.setParent(null);
    }

    public void insertChild(int idx, Command child) {
        if (child.hasParent()) {
            child.parent.removeChild(child);
        }
        child.setParent(this);
        child.setIndent(this.getIndent() + 1);
        children.insertElementAt(child, idx);
    }

    public Vector<Command> getChildren() {
        return children;
    }

    public Command getChildAt(int idx) {
        return children.get(idx);
    }

    public boolean hasChild() {
        return children != null && children.size() > 0;
    }

    public int getChildCount() {
        return children.size();
    }

    public Command getLastChild() {
        return children.lastElement();
    }

    public Enumeration<Command> children() {
        if (children == null) {
            return EMPTY_ENUMERATION;
        } else {
            return children.elements();
        }
    }


    public boolean hasPrevSibling() {
        if (parent == null)
            return false;
        return parent.getChildIndexOf(this) != 0;
    }

    public boolean hasNextSibling() {
        if (parent == null)
            return false;
        return parent.getChildIndexOf(this) < parent.getChildCount() - 1;
    }

    public boolean isAncestorOf(Command cmd) {
        if (cmd == null || cmd.getParent() == null )
            return false;
        return cmd.isDescentOf(this);
    }

    public boolean isDescentOf(Command cmd) {
        Command parent = getParent();
        if (parent == null)
            return false;
        else if (parent == cmd)
            return true;
        else {
            return parent.isDescentOf(cmd);
        }
    }

    public void addTestResult(TestResult result) {
        results.add(result);
    }

    public boolean hasTestResult() {
        return !results.isEmpty();
    }

    public Vector<TestResult> getTestResults() {
        return results;
    }

    public void clearTestResults() {
        results.clear();
    }

    public boolean resultsAllPassed() {
        for (TestResult result : results) {
            if (!result.isSuccess())
                return false;
        }
        return true;
    }

    public boolean resultsAllFailed() {
        for (TestResult result : results) {
            if (result.isSuccess())
                return false;
        }
        return true;
    }

    public void clearDebugInfo() {
        inDebug = false;
    }

    @Override
    public String toString() {
        StringBuffer sb = new StringBuffer();
        sb.append("Command[keyword=").append(keyword).append(", ").append("params=[");
        for (Iterator<String> iter = params.iterator(); iter.hasNext();) {
            sb.append(iter.next()).append(",");
        }
        sb.append("],");
        sb.append("isBreakPoint=");
        sb.append(isBreakPoint);
        sb.append("]");
        return sb.toString();
    }

    // this method is used by XStream, because XStream don't use default constructor
    // and we must do something here.
    private Object readResolve() {
        // do what you need to do here
        validationInfos = new HashMap<Integer, String>();
        results = new Vector<TestResult>();
        // at the end returns itself
        return this;
    }

    public void traverse(CommandVisitor visitor) {
        visitor.visit(this);
        if (this.hasChild()) {
            for (Command child : children) {
                child.traverse(visitor);
            }
        }
    }


    public boolean isEqualWith(Object obj) {
        if (obj instanceof Command) {
            Command c = (Command) obj;
            if (!keyword.equals(c.keyword))
                return false;
            else if (isBreakPoint != c.isBreakPoint)
                return false;
            else if (indent != c.indent)
                return false;
            else if (enabled != c.enabled )
                return false;
            else if (params.size() != c.params.size()) {
                return false;
            } else if (children.size() != c.children.size())
                return false;

            for (int i = 0; i < params.size(); i++) {
                String p = params.get(i);
                String p2 = c.params.get(i);
                if ((p == null && p2 != null) || (p!= null && p2 == null))
                    return false;
                if (p!= null && p2!= null && !p.equals(p2))
                    return false;
            }

            for (int i = 0; i < children.size(); i++) {
                Command child = children.get(i);
                Command child2 = c.children.get(i);
                if (!child.isEqualWith(child2))
                    return false;
            }
        } else {
            return false;
        }

        return true;
    }

    public void copyAttributesFrom(Command src) {
        keyword = src.keyword;
        isBreakPoint = src.isBreakPoint;
        indent = src.indent;
        expanded = src.expanded;
        enabled = src.enabled;
        params = src.params;
    }

    public Command clone() {
        Command copy = new Command();
        copy.keyword = new String(keyword);
        copy.isBreakPoint = isBreakPoint;
//        copy.uuid = uuid;
        copy.indent = indent;
        copy.expanded = expanded;
        copy.enabled = enabled;

        for (Command child : children) {
            Command copyChild = child.clone();
            copyChild.setParent(copy);
            copy.children.add(copyChild);
        }

        for (String v : params) {
            if (v == null)
                copy.params.add(v);
            else
                copy.params.add(new String(v));
        }
        return copy;
    }

    static public final Enumeration<Command> EMPTY_ENUMERATION
            = new Enumeration<Command>() {
        public boolean hasMoreElements() {
            return false;
        }

        public Command nextElement() {
            throw new NoSuchElementException("No more elements");
        }
    };
}
